package org.jugjobs.util;

public abstract class HTMLScanner {
	
	protected String s;
	protected int sIndex;
	
	public HTMLScanner(String myString) {
		s=myString;
	}
	
	private final static int STATE_BETWEEN_TAGS  = 1;
	private final static int STATE_WIKET_MATCHED = 2;
	private final static int STATE_SLASH_MATCHED = 3;
	private final static int STATE_IN_TAG_IDENT  = 4;
	private final static int STATE_IN_TAG_PARMS  = 5;
	private final static int STATE_IN_COMMENT    = 6;
	private final static int STATE_DONE          =99;
	private final static int EOF = -1;
	public void performScan() throws HTMLScanningException {
		sIndex=0;
		int state = STATE_BETWEEN_TAGS;
		StringBuffer scratchSB=new StringBuffer();
		while(state!=STATE_DONE) {
			// get the next character if any left or EOF if none
			int current = EOF;
			if(sIndex<s.length()) {
				current=s.charAt(sIndex++);
			}
			// decide what to do with the character depending on where we are
			switch(state) {
				// In this state we are not currently inside a tag.
				case STATE_BETWEEN_TAGS: {
					// if we hit eof between tags report any accumulated text and set done.
					if(current==EOF) {
						if(scratchSB.length()>0) {
							text(scratchSB.toString());
						}
						state = STATE_DONE;
					}
					// if we see a wicket report any accumulated text and set wicket matched.
					// reset the scratch SB to accumulate the tag id.
					else if(current=='<') {
						wicket();
						if(scratchSB.length()>0) {
							text(scratchSB.toString());
						}
						scratchSB = new StringBuffer();
						state=STATE_WIKET_MATCHED;
					}
					// otherwise, continue accumulating text.
					else {
						scratchSB.append((char)current);
					}
				}
				break;
			
				// in this state we have seen the wicket "<"
				case STATE_WIKET_MATCHED: {
					// this is not a legal place for an eof.  Report problem if encountered.
					if(current==EOF) {
						noteProblem("Unexpected EOF");
						state = STATE_DONE;
					}
					// if we see a slash (indicating close tag) add it to the tag in the scratch SB.
					else if(current=='/') {
						scratchSB.append((char)current);
						state=STATE_SLASH_MATCHED;
					}
					// if the character is a letter it is part of the tag ident.
					else if(Character.isLetter((char)current)) {
						scratchSB.append((char)current);
						state=STATE_IN_TAG_IDENT;
					}
					// ignore whitespace between the wicket and the tag ident.
					else if(Character.isWhitespace((char)current)) {
						// ignore leading spaces in tag
					}
					// if empty tag complain.
					else if(current=='>'){
						noteProblem("EmptyTag");
						closeWicket();
						state=proceedOnError()?STATE_BETWEEN_TAGS:STATE_DONE;
					}
					// an exclamation mark at this point introduces a comment.
					else if(current=='!'){
						state = STATE_IN_COMMENT;
					}
					else {
						noteProblem("Illegal Character in Tag: "+current);
						if(!proceedOnError()) {
							state=STATE_DONE;
						}
					}
				}
				break;
				
				// in this state we have seen wicket-slash ("</")
				case STATE_SLASH_MATCHED: {
					// not a good place for EOF.  Complain if encountered.
					if(current==EOF) {
						noteProblem("Unexpected EOF");
						state = STATE_DONE;
					}
					// accumulate characters into the tag ident.
					else if(Character.isLetter((char)current)) {
						scratchSB.append((char)current);
						state=STATE_IN_TAG_IDENT;
					}
					// dunno if space is allowed between slash and tag.  I assume it is.
					else if(Character.isWhitespace((char)current)) {
						// ignore leading spaces between slash and tag
					}
					// if tag is empty except for slash complain.
					else if(current=='>'){
						noteProblem("EmptyTag");
						scratchSB = new StringBuffer();
						closeWicket();
						state=proceedOnError()?STATE_BETWEEN_TAGS:STATE_DONE;
					}
					// any other character is illegal here.
					else {
						noteProblem("Illegal Character in Tag: "+current);
						if(!proceedOnError()) {
							state=STATE_DONE;
						}
					}
				}
				break;
				
				// in this state we have seen at least one character of the identifier.
				case STATE_IN_TAG_IDENT: {
					// this tag has no closing wicket.  complain.
					if(current==EOF) {
						noteProblem("Unexpected EOF");
						state = STATE_DONE;
					}
					// accumulate letters into the accumulator for the 
					else if(Character.isLetter((char)current)) {
						scratchSB.append((char)current);
					}
					// closing wicket -- emit tags and empty params.
					else if(current=='>') {
						tag(scratchSB.toString());
						tagParms("");
						closeWicket();
						scratchSB = new StringBuffer();
						state=STATE_BETWEEN_TAGS;
					}
					// anything else -- emit tags and begin accumulating parameters.
					else {
						tag(scratchSB.toString());
						scratchSB=new StringBuffer();
						scratchSB.append((char)current);
						state=STATE_IN_TAG_PARMS;
					}
				}
				break;
				
				// in this state we have seen the whole identifier and are accumulating parameters.
				case STATE_IN_TAG_PARMS: {
					// if eof before end of tag complain.
					if(current==EOF) {
						noteProblem("Unexpected EOF");
						state = STATE_DONE;
					}
					// close wicket -- emit params and go to between tags.
					else if(current=='>') {
						tagParms(scratchSB.toString());
						closeWicket();
						scratchSB = new StringBuffer();
						state=STATE_BETWEEN_TAGS;
					}
					// continue to accumulate params.
					else {
						scratchSB.append((char)current);
					}
				}
				break;
				
				// in this state we have seen a wicket bang ("<!") to open a comment
				case STATE_IN_COMMENT: {
					// if eof before end of tag complain.
					if(current==EOF) {
						noteProblem("Unexpected EOF");
						state = proceedOnError()?STATE_BETWEEN_TAGS:STATE_DONE;
					}
					// close wicket -- go to between tags.
					else if(current=='>') {
						comment(scratchSB.toString());
						scratchSB = new StringBuffer();
						state=STATE_BETWEEN_TAGS;
					}
					// continue to accumulate params.
					else {
						scratchSB.append((char)current);
					}
				}
				break;
			}
		}
	}
	protected abstract void noteProblem(String problem) throws HTMLScanningException;
	protected abstract void text(String s) throws HTMLScanningException;
	protected abstract void wicket() throws HTMLScanningException;
	protected abstract void closeWicket() throws HTMLScanningException;
	protected abstract void tag(String tag) throws HTMLScanningException;
	protected abstract void tagParms(String parms) throws HTMLScanningException;
	protected abstract boolean proceedOnError();
	protected void comment(String s) throws HTMLScanningException {
		// default behavior is to do nothing.  Concrete subclass can override.
	}
}
